package com.ruoyi.user.service.impl;

import com.ruoyi.common.utils.PageUtils;
import com.ruoyi.system.domain.SpffVideos;
import com.ruoyi.system.mapper.SpffVideosMapper;
import com.ruoyi.user.service.IVideoRecommendService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
//import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 基于Redis ZSet的视频推荐Service业务层处理
 * 
 * @author xjj
 * @date 2025-09-23
 */
@Service("redisVideoRecommendService")
@Primary
public class RedisVideoRecommendServiceImpl implements IVideoRecommendService
{
    @Autowired
    private SpffVideosMapper spffVideosMapper;
    
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;
    
    @Autowired
    private com.ruoyi.system.service.impl.SpffVideosServiceImpl spffVideosService;

    // Redis Key 常量
    private static final String HOT_VIDEOS_KEY = "hot:videos:global";
    private static final String HOT_CATEGORY_KEY = "hot:videos:category:";
    private static final String HOT_VIDEOS_HASH_KEY = "hot:videos:hash:global";
    private static final String HOT_CATEGORY_HASH_KEY = "hot:videos:hash:category:";
    private static final String USER_RECOMMEND_KEY = "recommend:user:";
    private static final String USER_RECOMMEND_HASH_KEY = "recommend:user:hash:";
    private static final String NEARBY_VIDEOS_KEY = "nearby:videos:";
    private static final String NEARBY_VIDEOS_HASH_KEY = "nearby:videos:hash:";
    
    // 缓存时间（秒）
    private static final long HOT_CACHE_TIME = 300; // 5分钟
    private static final long RECOMMEND_CACHE_TIME = 900; // 15分钟
    private static final long NEARBY_CACHE_TIME = 600; // 10分钟

    /**
     * 个性化推荐视频 - 使用Redis ZSet+Hash缓存用户推荐列表
     */
    @Override
    public List<SpffVideos> getPersonalizedRecommend(Long userId, Integer pageNum, Integer pageSize)
    {
        String zsetKey = USER_RECOMMEND_KEY + userId;
        String hashKey = USER_RECOMMEND_HASH_KEY + userId;
        ZSetOperations<Object, Object> zSetOps = redisTemplate.opsForZSet();
        
        // 1. 从ZSet获取排序后的视频ID（分页）
        int start = (pageNum - 1) * pageSize;
        int end = start + pageSize - 1;
        Set<Object> cachedVideoIds = zSetOps.reverseRange(zsetKey, start, end);
        
        if (cachedVideoIds != null && !cachedVideoIds.isEmpty()) {
            // 2. 从Hash批量获取完整视频信息
            List<Object> videoIdStrList = cachedVideoIds.stream()
                .map(id -> id.toString())
                .collect(Collectors.toList());
            
            List<Object> cachedVideos = redisTemplate.opsForHash().multiGet(hashKey, videoIdStrList);
            
            // 3. 转换并过滤null值
            List<SpffVideos> result = new ArrayList<>();
            for (Object videoObj : cachedVideos) {
                if (videoObj != null && videoObj instanceof SpffVideos) {
                    result.add((SpffVideos) videoObj);
                }
            }
            
            if (!result.isEmpty()) {
                System.out.println("从缓存获取用户 " + userId + " 个性化推荐视频 " + result.size() + " 个");
                return result;
            }
        }
        
        // 4. 缓存中没有数据，重新计算推荐列表
        System.out.println("用户 " + userId + " 推荐缓存未命中，重新计算推荐列表");
        List<SpffVideos> recommendVideos = calculatePersonalizedRecommend(userId);
        
        // 5. 将推荐结果存入Redis ZSet + Hash
        if (!recommendVideos.isEmpty()) {
            // 清除旧缓存
            redisTemplate.delete(zsetKey);
            redisTemplate.delete(hashKey);
            
            Map<Object, Double> scoreMap = new HashMap<>();
            Map<Object, Object> videoMap = new HashMap<>();
            
            for (SpffVideos video : recommendVideos) {
                double score = calculateRecommendScore(video);
                scoreMap.put(video.getId(), score);
                videoMap.put(video.getId().toString(), video);
            }
            
            // 批量添加到ZSet（用于排序和分页）
            for (Map.Entry<Object, Double> entry : scoreMap.entrySet()) {
                zSetOps.add(zsetKey, entry.getKey(), entry.getValue());
            }
            
            // 批量添加到Hash（存储完整视频对象）
            if (!videoMap.isEmpty()) {
                redisTemplate.opsForHash().putAll(hashKey, videoMap);
            }
            
            // 设置过期时间
            redisTemplate.expire(zsetKey, RECOMMEND_CACHE_TIME, java.util.concurrent.TimeUnit.SECONDS);
            redisTemplate.expire(hashKey, RECOMMEND_CACHE_TIME, java.util.concurrent.TimeUnit.SECONDS);
            
            System.out.println("用户 " + userId + " 推荐列表已缓存，共 " + recommendVideos.size() + " 个视频");
        }
        
        // 6. 分页返回结果
        int endIndex = Math.min(start + pageSize, recommendVideos.size());
        return recommendVideos.subList(start, endIndex);
    }

    /**
     * 获取热门视频榜单 - 直接从Redis Hash获取完整视频信息
     */
    @Override
    public List<SpffVideos> getHotVideos(String type, Long categoryId, Integer pageNum, Integer pageSize)
    {
        String zsetKey = "category".equals(type) ? 
            HOT_CATEGORY_KEY + categoryId : HOT_VIDEOS_KEY;
        String hashKey = "category".equals(type) ? 
            HOT_CATEGORY_HASH_KEY + categoryId : HOT_VIDEOS_HASH_KEY;
        
        ZSetOperations<Object, Object> zSetOps = redisTemplate.opsForZSet();
        
        // 1. 从ZSet获取排序后的视频ID（分页）
        int start = (pageNum - 1) * pageSize;
        int end = start + pageSize - 1;
        Set<Object> cachedVideoIds = zSetOps.reverseRange(zsetKey, start, end);
        
        if (cachedVideoIds != null && !cachedVideoIds.isEmpty()) {
            // 2. 从Hash批量获取完整视频信息
            List<Object> videoIdStrList = cachedVideoIds.stream()
                .map(id -> id.toString())
                .collect(Collectors.toList());
            
            List<Object> cachedVideos = redisTemplate.opsForHash().multiGet(hashKey, videoIdStrList);
            
            // 3. 转换并过滤null值
            List<SpffVideos> result = new ArrayList<>();
            for (Object videoObj : cachedVideos) {
                if (videoObj != null && videoObj instanceof SpffVideos) {
                    result.add((SpffVideos) videoObj);
                }
            }
            
            if (!result.isEmpty()) {
                System.out.println("从缓存获取热门视频 " + result.size() + " 个，类型: " + type + 
                    (categoryId != null ? "，分类ID: " + categoryId : ""));
                return result;
            }
        }
        
        // 4. 缓存中没有数据，从数据库查询（由定时任务更新，这里作为降级处理）
        System.out.println("热门视频缓存未命中，从数据库查询，类型: " + type + 
            (categoryId != null ? "，分类ID: " + categoryId : ""));
            
        // 清理分页上下文，防止分页插件干扰手动分页
        PageUtils.clearPage();
            
        Map<String, Object> params = new HashMap<>();
        params.put("type", type);
        params.put("categoryId", categoryId);
        params.put("offset", start);
        params.put("limit", pageSize);
        
        return spffVideosMapper.selectHotVideos(params);
    }

    /**
     * 获取附近的视频 - 使用Redis ZSet+Hash按距离排序并缓存完整信息
     */
    @Override
    public List<SpffVideos> getNearbyVideos(BigDecimal latitude, BigDecimal longitude, Integer radius, Integer pageNum, Integer pageSize)
    {
        // 生成地理位置的缓存Key（按经纬度区域划分）
        String locationKey = generateLocationKey(latitude, longitude, radius);
        String zsetKey = NEARBY_VIDEOS_KEY + locationKey;
        String hashKey = NEARBY_VIDEOS_HASH_KEY + locationKey;
        
        ZSetOperations<Object, Object> zSetOps = redisTemplate.opsForZSet();
        
        // 1. 从ZSet获取按距离排序的视频ID（分页）
        int start = (pageNum - 1) * pageSize;
        int end = start + pageSize - 1;
        Set<Object> cachedVideoIds = zSetOps.range(zsetKey, start, end); // 按距离正序
        
        if (cachedVideoIds != null && !cachedVideoIds.isEmpty()) {
            // 2. 从Hash批量获取完整视频信息
            List<Object> videoIdStrList = cachedVideoIds.stream()
                .map(id -> id.toString())
                .collect(Collectors.toList());
            
            List<Object> cachedVideos = redisTemplate.opsForHash().multiGet(hashKey, videoIdStrList);
            
            // 3. 转换并过滤null值
            List<SpffVideos> result = new ArrayList<>();
            for (Object videoObj : cachedVideos) {
                if (videoObj != null && videoObj instanceof SpffVideos) {
                    result.add((SpffVideos) videoObj);
                }
            }
            
            if (!result.isEmpty()) {
                System.out.println("从缓存获取附近视频 " + result.size() + " 个，位置: " + 
                    latitude + "," + longitude + "，半径: " + radius + "km");
                return result;
            }
        }
        
        // 4. 缓存中没有数据，从数据库查询
        System.out.println("附近视频缓存未命中，从数据库查询，位置: " + 
            latitude + "," + longitude + "，半径: " + radius + "km");
            
        // 清理分页上下文，防止分页插件干扰手动分页
        PageUtils.clearPage();
            
        double latRange = radius / 111.0;
        double lngRange = radius / (111.0 * Math.cos(Math.toRadians(latitude.doubleValue())));
        
        Map<String, Object> params = new HashMap<>();
        params.put("minLatitude", latitude.subtract(new BigDecimal(latRange)));
        params.put("maxLatitude", latitude.add(new BigDecimal(latRange)));
        params.put("minLongitude", longitude.subtract(new BigDecimal(lngRange)));
        params.put("maxLongitude", longitude.add(new BigDecimal(lngRange)));
        params.put("offset", 0);
        params.put("limit", 100);
        
        List<SpffVideos> nearbyVideos = spffVideosMapper.selectNearbyVideos(params);
        
        // 5. 计算距离并存入Redis ZSet + Hash
        if (!nearbyVideos.isEmpty()) {
            // 清除旧缓存
            redisTemplate.delete(zsetKey);
            redisTemplate.delete(hashKey);
            
            Map<Object, Double> distanceMap = new HashMap<>();
            Map<Object, Object> videoMap = new HashMap<>();
            
            for (SpffVideos video : nearbyVideos) {
                if (video.getLatitude() != null && video.getLongitude() != null) {
                    double distance = calculateDistance(
                        latitude.doubleValue(), longitude.doubleValue(),
                        video.getLatitude().doubleValue(), video.getLongitude().doubleValue()
                    );
                    distanceMap.put(video.getId(), distance);
                    videoMap.put(video.getId().toString(), video);
                }
            }
            
            // 批量添加到ZSet（用于按距离排序）
            for (Map.Entry<Object, Double> entry : distanceMap.entrySet()) {
                zSetOps.add(zsetKey, entry.getKey(), entry.getValue());
            }
            
            // 批量添加到Hash（存储完整视频对象）
            if (!videoMap.isEmpty()) {
                redisTemplate.opsForHash().putAll(hashKey, videoMap);
            }
            
            // 设置过期时间
            redisTemplate.expire(zsetKey, NEARBY_CACHE_TIME, java.util.concurrent.TimeUnit.SECONDS);
            redisTemplate.expire(hashKey, NEARBY_CACHE_TIME, java.util.concurrent.TimeUnit.SECONDS);
            
            System.out.println("附近视频已缓存，共 " + nearbyVideos.size() + " 个视频");
        }
        
        // 6. 分页返回结果
        int endIndex = Math.min(start + pageSize, nearbyVideos.size());
        return nearbyVideos.subList(start, endIndex);
    }

    /**
     * 获取最新视频
     */
    @Override
    public List<SpffVideos> getLatestVideos(Long categoryId, Integer pageNum, Integer pageSize)
    {
        // 清理分页上下文，防止分页插件干扰手动分页
        PageUtils.clearPage();
        
        Map<String, Object> params = new HashMap<>();
        params.put("categoryId", categoryId);
        params.put("offset", (pageNum - 1) * pageSize);
        params.put("limit", pageSize);
        
        return spffVideosMapper.selectLatestVideos(params);
    }

    /**
     * 随机获取视频
     */
    @Override
    public List<SpffVideos> getRandomVideos(Integer size)
    {
        // 清理分页上下文，防止分页插件干扰手动分页
        PageUtils.clearPage();
        
        Map<String, Object> params = new HashMap<>();
        params.put("limit", size);
        
        return spffVideosMapper.selectRandomVideos(params);
    }

    /**
     * 计算个性化推荐
     */
    private List<SpffVideos> calculatePersonalizedRecommend(Long userId) {
        // 清理分页上下文，防止分页插件干扰手动分页
        PageUtils.clearPage();
        
        Map<String, Object> params = new HashMap<>();
        params.put("userId", userId);
        params.put("offset", 0);
        params.put("limit", 100);
        
        List<SpffVideos> result = spffVideosMapper.selectPersonalizedRecommend(params);
        
        // 如果推荐结果不足，补充热门视频
        if (result.size() < 50) {
            List<SpffVideos> hotVideos = getHotVideos("global", null, 1, 50);
            hotVideos.removeIf(video -> result.stream().anyMatch(r -> r.getId().equals(video.getId())));
            result.addAll(hotVideos);
        }
        
        return result;
    }

    /**
     * 计算推荐得分
     */
    private double calculateRecommendScore(SpffVideos video) {
        long viewCount = video.getViewCount() != null ? video.getViewCount() : 0;
        long likeCount = video.getLikeCount() != null ? video.getLikeCount() : 0;
        long commentCount = video.getCommentCount() != null ? video.getCommentCount() : 0;
        long shareCount = video.getShareCount() != null ? video.getShareCount() : 0;
        long collectCount = video.getCollectCount() != null ? video.getCollectCount() : 0;
        
        return viewCount * 0.3 + likeCount * 8 + commentCount * 15 + shareCount * 25 + collectCount * 20;
    }

    /**
     * 计算热度得分
     */
    private double calculateHotScore(SpffVideos video) {
        return calculateRecommendScore(video);
    }

    /**
     * 根据视频ID列表获取视频详情，包含当前用户点赞状态
     */
    private List<SpffVideos> getVideosByIds(List<Long> videoIds) {
        if (videoIds == null || videoIds.isEmpty()) {
            return new ArrayList<>();
        }
        
        Map<String, Object> params = new HashMap<>();
        params.put("videoIds", videoIds);
        
        // 这里需要在Mapper中添加根据ID列表查询的方法
        List<SpffVideos> videos = spffVideosMapper.selectSpffVideosByIds(videoIds);
        
        // 推荐列表不设置点赞状态，提高性能
        // 用户点击进入视频详情页时才获取点赞状态
        
        // 按照传入的ID顺序排序
        Map<Long, SpffVideos> videoMap = videos.stream()
            .collect(Collectors.toMap(SpffVideos::getId, v -> v));
        
        return videoIds.stream()
            .map(videoMap::get)
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
    }

    /**
     * 生成地理位置缓存Key
     */
    private String generateLocationKey(BigDecimal latitude, BigDecimal longitude, Integer radius) {
        // 将经纬度按0.01度（约1公里）分区
        int latGrid = (int) Math.floor(latitude.doubleValue() * 100);
        int lngGrid = (int) Math.floor(longitude.doubleValue() * 100);
        return latGrid + "_" + lngGrid + "_" + radius;
    }

    /**
     * 计算两点之间的距离（使用Haversine公式）
     */
    private double calculateDistance(double lat1, double lng1, double lat2, double lng2) {
        final int R = 6371; // 地球半径（公里）
        
        double latDistance = Math.toRadians(lat2 - lat1);
        double lngDistance = Math.toRadians(lng2 - lng1);
        
        double a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2)
                + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
                * Math.sin(lngDistance / 2) * Math.sin(lngDistance / 2);
        
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        
        return R * c;
    }
    
    /**
     * 获取当前登录用户ID
     * 
     * @return 用户ID
     */
    private Long getCurrentUserId() {
        try {
            return com.ruoyi.common.utils.SecurityUtils.getUserId();
        } catch (Exception e) {
            return null; // 未登录用户返回null
        }
    }
}
